#include "stdafx.h"
#include <engine/Map.hpp>
#include <engine/MapRenderer.hpp>
#include <engine/CamerManager.hpp>
#include <engine/TerrainTile.hpp>
#include "wxOgre.h"

#ifdef __WXGTK__
#include <gdk/gdk.h>
#include <gtk/gtk.h> // just this should suffice as it should include gdk.h itself
/* Seems to be needed under Linux */
#include <wx/gtk/win_gtk.h>
#include <gdk/gdkx.h>
#include <GL/glx.h>
#endif

namespace
{
	// Required for the timer
	const long ID_RENDERTIMER = wxNewId();
	const long ID_SCROLL_TIMER = wxNewId();
}

// Required for WX
IMPLEMENT_CLASS(wxOgre, wxControl)

// Required for WX
BEGIN_EVENT_TABLE(wxOgre, wxControl)
	EVT_SIZE(wxOgre::OnSize)
	EVT_ERASE_BACKGROUND( wxOgre::OnEraseBackground )
	EVT_TIMER(ID_RENDERTIMER, wxOgre::OnRenderTimer )
	EVT_MOTION(wxOgre::on_mouse_move)
	EVT_KEY_DOWN(wxOgre::on_key_down)
	EVT_LEAVE_WINDOW(wxOgre::on_mouse_leave)
	EVT_ENTER_WINDOW(wxOgre::on_mouse_enter)
	EVT_LEFT_DOWN(wxOgre::on_mouse_lbutton_down)
	EVT_TIMER(ID_SCROLL_TIMER, wxOgre::on_scroll_timer)
END_EVENT_TABLE()

wxOgre::wxOgre(wxFrame* parent, Ogre::RenderSystem* renderSystem)
: wxControl(parent, -1), mTimer(this, ID_RENDERTIMER),
	m_pLogManager(new Ogre::LogManager()),
	m_pRoot(nullptr), mViewPort(0),
	mSceneMgr(0), mRenderWindow(0), mIsInitialized(false),
	m_ScrollTimer(this, ID_SCROLL_TIMER), m_LastDeep(0),
	m_WorldWidthRatio(40), m_WorldHeightRatio(40),
	m_pLastTile(nullptr),
	m_HighlightSize(1)
{
	m_pLogManager->createLog("Ogre.log", true, true);
	m_pRoot.reset(new Ogre::Root());
	// Create all Ogre objects
	createOgreRenderWindow(renderSystem);
	// Create light manager
	m_pLightManager.reset(new he::LightManager(*getSceneManager()));
	// Create camera manager.
	m_pCameraManager.reset(new he::CameraManager(*getSceneManager(), *mRenderWindow, "Main"));
	// Set up resources
	setup_resources();
	// Create world
	m_pWorld.reset(new he::World(he::Map::create_map(30, 60), he::settings::g_RuntimePath));
	// Create world renderer
	m_pWorldRenderer.reset(new he::WorldRenderer(*getSceneManager(), *m_pWorld));
	// Connect to terrain director signals
	m_TerrainDirector.on_tile_size(boost::bind(&wxOgre::set_high_light_size, this, _1));
	// Create the overlay
	m_pDebugOverlay = Ogre::OverlayManager::getSingleton().getByName("Core/DebugOverlay");
	m_pDebugOverlay->show();
	// Start the update stats timer
	toggleTimerRendering();
	mIsInitialized = true;
}

wxOgre::~wxOgre()
{
	// Destroy ray scene query
	mSceneMgr->destroyQuery(m_pRaySceneQuery);
	// destroy Viewport and RenderWindow
	if (mViewPort)
		mRenderWindow->removeViewport(mViewPort->getZOrder());
	Ogre::Root::getSingleton().detachRenderTarget(mRenderWindow);
	delete mRenderWindow;
}

void wxOgre::createOgreRenderWindow(Ogre::RenderSystem* renderSystem)
{
	// If we got an Ogre::RenderSystem, we'll use that
	if(renderSystem)
		m_pRoot->setRenderSystem(renderSystem);
	// If not, maybe the Root already has one
	else if(!m_pRoot->getRenderSystem())
	{
		// At this point there are no rendersystems, so we
		// can try to load in the previous configuration
		if(!m_pRoot->restoreConfig())
		{
			// That failed so we have to show the Dialog
			if(!m_pRoot->showConfigDialog())
			{
				// If the user canceled that there's nothing else we can do!
				OGRE_EXCEPT(Ogre::Exception::ERR_INVALIDPARAMS, "No RenderSystem chosen", "wxOgre::createOgreRenderWindow");
			}
		}
	}
	mRenderWindow = m_pRoot->initialise(false);
	// Create a new parameters list according to compiled OS
	Ogre::NameValuePairList params;
	Ogre::String handle;
#ifdef __WXMSW__
	handle = Ogre::StringConverter::toString((size_t)((HWND)GetHandle()));
#elif defined(__WXGTK__)
	// TODO: Someone test this. you might to use "parentWindowHandle" if this
	// does not work.  Ogre 1.2 + Linux + GLX platform wants a string of the
	// format display:screen:window, which has variable types ulong:uint:ulong.
	GtkWidget* widget = GetHandle();
	gtk_widget_realize( widget );	// Mandatory. Otherwise, a segfault happens.
	std::stringstream handleStream;
	Display* display = GDK_WINDOW_XDISPLAY( widget->window );
	Window wid = GDK_WINDOW_XWINDOW( widget->window );	// Window is a typedef for XID, which is a typedef for unsigned int
	/* Get the right display (DisplayString() returns ":display.screen") */
	std::string displayStr = DisplayString( display );
	displayStr = displayStr.substr( 1, ( displayStr.find( ".", 0 ) - 1 ) );
	/* Put all together */
	handleStream << displayStr << ':' << DefaultScreen( display ) << ':' << wid;
	handle = handleStream.str();
#else
#error Not supported on this platform.
#endif
	params["externalWindowHandle"] = handle;
	// Get wx control window size
	int width;
	int height;
	GetSize(&width, &height);
	// Create the render window
	mRenderWindow =
		Ogre::Root::getSingleton().createRenderWindow("OgreRenderWindow", width, height, false, &params);
	// Create the SceneManager, in this case a generic one
	mSceneMgr = m_pRoot->createSceneManager(Ogre::ST_EXTERIOR_FAR, "MainSceneManager");
	// Create ray scene query object
	m_pRaySceneQuery = mSceneMgr->createRayQuery(Ogre::Ray(Ogre::Vector3(0, 0, 0),
		Ogre::Vector3::NEGATIVE_UNIT_Y));
}

void wxOgre::setup_resources()
{
	// Load resource paths from config file
	Ogre::ConfigFile cf;
	cf.load("resources.cfg");
	// Go through all sections & settings in the file
	Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();
	Ogre::String secName, typeName, archName;
	while (seci.hasMoreElements())
	{
		secName = seci.peekNextKey();
		Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();
		Ogre::ConfigFile::SettingsMultiMap::iterator i;
		for (i = settings->begin(); i != settings->end(); ++i)
		{
			typeName = i->first;
			archName = i->second;
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
			// OS X does not set the working directory relative to the app,
			// In order to make things portable on OS X we need to provide
			// the loading with it's own bundle path location
			Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
				String(macBundlePath() + "/" + archName), typeName, secName);
#else
			Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
				archName, typeName, secName);
#endif
		}
	} 
	// Initialize, parse scripts etc
	Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
	// Get overlays
	m_pGUICurrent = Ogre::OverlayManager::getSingleton().getOverlayElement("Core/CurrFps");
	m_pGUITriangles = Ogre::OverlayManager::getSingleton().getOverlayElement("Core/NumTris");
	m_pGUIBatches = Ogre::OverlayManager::getSingleton().getOverlayElement("Core/NumBatches");
}


void wxOgre::toggleTimerRendering()
{
	// Toggle Start/Stop
	if (mTimer.IsRunning())
		mTimer.Stop();
	mTimer.Start(100);
}

void wxOgre::OnSize(wxSizeEvent& event)
{
	if(mIsInitialized)
	{
		// Setting new size;
		int width;
		int height;
		GetSize(&width, &height);
		// Letting Ogre know the window has been resized;
		mRenderWindow->windowMovedOrResized();

		m_pCameraManager->set_camera_position(width / m_WorldWidthRatio, height / m_WorldHeightRatio, 0);
	}
}


void wxOgre::OnEraseBackground( wxEraseEvent& )
{
	if(mIsInitialized)
		update();
}
void wxOgre::OnRenderTimer(wxTimerEvent& event)
{
	if(mIsInitialized)
		update_stats();
}

void wxOgre::update()
{
	Ogre::Root::getSingletonPtr()->renderOneFrame();
}

void wxOgre::update_stats()
{
	static std::string const cur_fps = "Current FPS: ";
	static std::string const best_fps = "Best FPS: ";
	static std::string const worst_fps = "Worst FPS: ";
	static std::string const triangles = "Triangle Count: ";
	static std::string const batches = "Batch Count: ";
	// Get elements
//	Ogre::OverlayElement* gui_curr =
//		Ogre::OverlayManager::getSingleton().getOverlayElement("Core/CurrFps");
//	Ogre::OverlayElement* gui_best =
//		Ogre::OverlayManager::getSingleton().getOverlayElement("Core/BestFps");
//	Ogre::OverlayElement* gui_worst =
//		Ogre::OverlayManager::getSingleton().getOverlayElement("Core/WorstFps");
//	Ogre::OverlayElement* gui_tris =
//		Ogre::OverlayManager::getSingleton().getOverlayElement("Core/NumTris");
//	Ogre::OverlayElement* gui_batches =
//		Ogre::OverlayManager::getSingleton().getOverlayElement("Core/NumBatches");
	// Get stats
	Ogre::RenderTarget::FrameStats const &stats = mRenderWindow->getStatistics();
	// Set the data
//	gui_curr->setCaption(cur_fps + Ogre::StringConverter::toString(stats.lastFPS));
//	gui_best->setCaption(best_fps + Ogre::StringConverter::toString(stats.bestFPS)
//		+ " " + Ogre::StringConverter::toString(stats.bestFrameTime)+" ms");
//	gui_worst->setCaption(worst_fps + Ogre::StringConverter::toString(stats.worstFPS)
//		+" " + Ogre::StringConverter::toString(stats.worstFrameTime)+" ms");
	m_pGUICurrent->setCaption(cur_fps + Ogre::StringConverter::toString(stats.lastFPS));
	m_pGUITriangles ->setCaption(triangles + Ogre::StringConverter::toString(stats.triangleCount));
	m_pGUIBatches->setCaption(batches + Ogre::StringConverter::toString(stats.batchCount));
}

void wxOgre::show_wireframe(bool Show)
{
	if(Show)
		m_pCameraManager->main_camera().setPolygonMode(Ogre::PM_WIREFRAME);
	else
		m_pCameraManager->main_camera().setPolygonMode(Ogre::PM_SOLID);
}

void wxOgre::enable_noise(bool Enable)
{
	get_map_renderer().enable_noise(Enable);
}

Ogre::Ray wxOgre::get_world_from_screen(long X, long Y, he::MapRenderer::RayEntryContainer_t &Vectors)
{
	// Get the world coordinates
	Ogre::Ray ray = m_pCameraManager->main_camera().getCameraToViewportRay(float(X) / mRenderWindow->getWidth(),
		float(Y) / mRenderWindow->getHeight());
	m_pRaySceneQuery->setRay(ray);
	Ogre::RaySceneQueryResult &rs = m_pRaySceneQuery->execute();
	// Copy to vector
	assert(Vectors.size() == 0);
	std::copy(rs.begin(), rs.end(), std::back_inserter(Vectors));

	return ray;
}

void wxOgre::move_camera(int X, int Z)
{
	m_pCameraManager->move_main_camera_relative(Ogre::Vector3(X, 0, Z));
}

void wxOgre::on_mouse_move(wxMouseEvent &Event)
{
	m_ScrollDir = SD_NONE;
	// Only if left button is not down
	if(!Event.LeftIsDown())
	{
		// Move the camera if we are at the edge
		if(Event.GetX() > GetClientSize().GetWidth() - SCROLL_OFFSET)
			m_ScrollDir = SD_RIGHT;
		else if(Event.GetX() < SCROLL_OFFSET)
			m_ScrollDir = SD_LEFT;
		else if(Event.GetY() < SCROLL_OFFSET)
			m_ScrollDir = SD_TOP;
		else if(Event.GetY() > GetClientSize().GetHeight() - SCROLL_OFFSET)
			m_ScrollDir = SD_BOTTOM;
		// Hilite the tile if we aren't scrolling
		else
		{
			if(m_ScrollTimer.IsRunning())
				m_ScrollTimer.Stop();
			// Get the ray intersections
			he::MapRenderer::RayEntryContainer_t ray_container;
			Ogre::Ray ray = get_world_from_screen(Event.GetX(), Event.GetY(), ray_container);
			// Highlite the tile
			if(he::TerrainTile const *p_tile = get_map_renderer().get_tile(ray_container, ray))
				get_map_renderer().highlight_tile(*p_tile, m_HighlightSize);
			// Render
			update();
		}
		// Timer for scrolling not running an we want scroll
		if(m_ScrollDir != SD_NONE && !m_ScrollTimer.IsRunning())
			m_ScrollTimer.Start(100);
	}
	// We are modifying the terrain
	else
	{
		long deep = get_base_mouse_pos().y - Event.GetY() - m_LastDeep;
		m_LastDeep = get_base_mouse_pos().y - Event.GetY();
		// Modify map
		get_map().set_tile_deep(*m_pLastTile, deep, m_HighlightSize, 1.0);
		// Get all neigbors that need to be updated
		he::TileContainer_t neighbors;
		get_map().get_all_outline_neighbors(*m_pLastTile, m_HighlightSize, neighbors);
		// Update
		get_map_renderer().update_tile(neighbors);
		// Update only
		// Hilite the tile but do not update the position of selected tile
		get_map_renderer().highlight_tile(*m_pLastTile, m_HighlightSize);
		// Render
		update();
	}
}

void wxOgre::on_key_down(wxKeyEvent &Event)
{
	switch(Event.GetKeyCode())
	{
	case WXK_LEFT:
		m_ScrollDir = SD_LEFT;
		break;
	case WXK_RIGHT:
		m_ScrollDir = SD_RIGHT;
		break;
	case WXK_UP:
		m_ScrollDir = SD_TOP;
		break;
	case WXK_DOWN:
		m_ScrollDir = SD_BOTTOM;
		break;
	case 'C':
		// Switch debug camera
		m_pCameraManager->switch_debug_camera();
		break;
	}
	scroll();
	m_ScrollDir = SD_NONE;
	update();
}

void wxOgre::on_mouse_leave(wxMouseEvent &Event)
{
	// Stop the scrolling
	m_ScrollDir = SD_NONE;
	if(m_ScrollTimer.IsRunning())
		m_ScrollTimer.Stop();
}

void wxOgre::on_mouse_lbutton_down(wxMouseEvent &Event)
{
	m_MousePos = Event.GetPosition();
	m_LastDeep = 0;
	// Save tile
	he::MapRenderer::RayEntryContainer_t ray_container;
	Ogre::Ray ray = get_world_from_screen(Event.GetX(), Event.GetY(), ray_container);
	m_pLastTile = get_map_renderer().get_tile(ray_container, ray);
}

void wxOgre::on_scroll_timer(wxTimerEvent& Event)
{
	scroll();
	update();
}

void wxOgre::set_high_light_size(unsigned char Size)
{
	m_HighlightSize = Size;
}

void wxOgre::scroll()
{
	switch(m_ScrollDir)
	{
	case SD_LEFT:
		move_camera(-1, 0);
		break;
	case SD_RIGHT:
		move_camera(1, 0);
		break;
	case SD_TOP:
		move_camera(0, -1);
		break;
	case SD_BOTTOM:
		move_camera(0, 1);
		break;
	}
}

wxPoint const &wxOgre::get_base_mouse_pos() const
{
	return m_MousePos;
}

void wxOgre::on_mouse_enter(wxMouseEvent &Event)
{
	if(FindFocus() != this)
		SetFocus();
}
